/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief FBO sRGB tests.
*//*--------------------------------------------------------------------*/

#include "es31fFboSRGBWriteControlTests.hpp"
#include "es31fFboTestUtil.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"
#include "tcuTestLog.hpp"
#include "glwEnums.hpp"
#include "sglrContextUtil.hpp"
#include "glwFunctions.hpp"
#include "deUniquePtr.hpp"
#include "deSharedPtr.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "glsTextureTestUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluStrUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

tcu::Vec4 getTestColorLinear (void)
{
	return tcu::Vec4(0.2f, 0.3f, 0.4f, 1.0f);
}

tcu::Vec4 getTestColorSRGB (void)
{
	return linearToSRGB(tcu::Vec4(0.2f, 0.3f, 0.4f, 1.0f));
}

tcu::Vec4 getTestColorBlank (void)
{
	return tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
}

tcu::Vec4 getEpsilonError (void)
{
	return tcu::Vec4(0.005f);
}

enum QueryType
{
	QUERYTYPE_ISENABLED = 0,
	QUERYTYPE_BOOLEAN,
	QUERYTYPE_FLOAT,
	QUERYTYPE_INT,
	QUERYTYPE_INT64,
	QUERYTYPE_LAST
};

enum DataType
{
	DATATYPE_BOOLEAN = 0,
	DATATYPE_FLOAT,
	DATATYPE_INT,
	DATATYPE_INT64,
};

enum FramebufferSRGB
{
	FRAMEBUFFERSRGB_ENABLED = 0,
	FRAMEBUFFERSRGB_DISABLED
};

enum FramebufferBlend
{
	FRAMEBUFFERBLEND_ENABLED = 0,
	FRAMEBUFFERBLEND_DISABLED
};

enum TextureSourcesType
{
	TEXTURESOURCESTYPE_RGBA		= 0,
	TEXTURESOURCESTYPE_SRGBA,
	TEXTURESOURCESTYPE_BOTH,
	TEXTURESOURCESTYPE_NONE
};

enum FboType
{
	FBOTYPE_SOURCE			= 0,
	FBOTYPE_DESTINATION
};

enum RendererTask
{
	RENDERERTASK_DRAW = 0,
	RENDERERTASK_COPY
};

enum SamplingType
{
	SAMPLINGTYPE_TEXTURE			= 0,
	SAMPLINGTYPE_TEXTURE_LOD,
	SAMPLINGTYPE_TEXTURE_GRAD,
	SAMPLINGTYPE_TEXTURE_OFFSET,
	SAMPLINGTYPE_TEXTURE_PROJ,
};

namespace TestTextureSizes
{
	const int WIDTH = 128;
	const int HEIGHT = 128;
} // global test texture sizes

namespace SampligTypeCount
{
	const int MAX = 5;
} // global max number of sampling types

std::string buildSamplingPassType (const int samplerTotal)
{
	std::ostringstream	shaderFragment;

	const SamplingType	samplingTypeList [] =
	{
		SAMPLINGTYPE_TEXTURE, SAMPLINGTYPE_TEXTURE_LOD, SAMPLINGTYPE_TEXTURE_GRAD, SAMPLINGTYPE_TEXTURE_OFFSET, SAMPLINGTYPE_TEXTURE_PROJ
	} ;

	for (int samplerTypeIdx = 0; samplerTypeIdx < DE_LENGTH_OF_ARRAY(samplingTypeList); samplerTypeIdx++)
	{
		shaderFragment
			<< "	if (uFunctionType == " << samplerTypeIdx << ") \n"
			<< "	{ \n";

		for (int samplerIdx = 0; samplerIdx < samplerTotal; samplerIdx++)
		{
			switch (static_cast<SamplingType>(samplerTypeIdx))
			{
				case SAMPLINGTYPE_TEXTURE:
				{
					shaderFragment
						<< "		texelColor" << samplerIdx << " = texture(uTexture" << samplerIdx << ", vs_aTexCoord); \n";
					break;
				}
				case SAMPLINGTYPE_TEXTURE_LOD:
				{
					shaderFragment
						<< "		texelColor" << samplerIdx << " = textureLod(uTexture" << samplerIdx << ", vs_aTexCoord, 0.0f); \n";
					break;
				}
				case SAMPLINGTYPE_TEXTURE_GRAD:
				{
					shaderFragment
						<< "		texelColor" << samplerIdx << " = textureGrad(uTexture" << samplerIdx << ", vs_aTexCoord, vec2(0.0f, 0.0f), vec2(0.0f, 0.0f)); \n";
					break;
				}
				case SAMPLINGTYPE_TEXTURE_OFFSET:
				{
					shaderFragment
						<< "		texelColor" << samplerIdx << " = textureOffset(uTexture" << samplerIdx << ", vs_aTexCoord, ivec2(0.0f, 0.0f)); \n";
					break;
				}
				case SAMPLINGTYPE_TEXTURE_PROJ:
				{
					shaderFragment
						<< "		texelColor" << samplerIdx << " = textureProj(uTexture" << samplerIdx << ", vec3(vs_aTexCoord, 1.0f)); \n";
					break;
				}
				default:
					DE_FATAL("Error: sampling type unrecognised");
			}
		}

		shaderFragment
			<< "	} \n";
	}

	return shaderFragment.str();
}

void logColor (Context& context, const std::string& colorLogMessage, const tcu::Vec4 resultColor)
{
	tcu::TestLog&			log		= context.getTestContext().getLog();
	std::ostringstream		message;

	message << colorLogMessage << " = (" << resultColor.x() << ", " << resultColor.y() << ", " << resultColor.z() << ", " << resultColor.w() << ")";
		log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}

struct TestFunction
{
	explicit TestFunction	(const bool hasFunctionValue)
		: hasFunction		(hasFunctionValue) {}
	TestFunction			(const char* const functionNameValue, const char* const functionDefinition)
		: hasFunction		(true)
		, functionName		(functionNameValue)
		, functionDefintion	(functionDefinition) {}
	~TestFunction			(void) {}

	bool			hasFunction;
	const char*		functionName;
	const char*		functionDefintion;
};

TestFunction getFunctionBlendLinearToSRGBCheck (void)
{
	const char* const functionName = "blendPlusLinearToSRGB";

	const char* const functionDefinition =
		"mediump vec4 blendPlusLinearToSRGB(in mediump vec4 colorSrc, in mediump vec4 colorDst) \n"
		"{ \n"
		"	const int MAX_VECTOR_SIZE = 4; \n"
		"	mediump vec4 colorConverted; \n"
		"	mediump vec4 colorBlended; \n"
		"	for (int idx = 0; idx < MAX_VECTOR_SIZE; idx++) \n"
		"	{ \n"
		"		if (uBlendFunctionType == 0) \n"
		"		{ \n"
		"			colorBlended[idx] = (colorSrc[idx] * uFactorSrc) + colorDst[idx] * uFactorDst; \n"
		"		} \n"
		"		if (uBlendFunctionType == 1) \n"
		"		{ \n"
		"			colorBlended[idx] = (colorSrc[idx] * uFactorSrc) - (colorDst[idx] * uFactorDst); \n"
		"		} \n"
				"if (uBlendFunctionType == 2) \n"
		"		{ \n"
		"			colorBlended[idx] = (colorDst[idx] * uFactorDst) - (colorSrc[idx] * uFactorSrc); \n"
		"		} \n"
		"		if (colorBlended[idx] < 0.0031308f) \n"
		"		{ \n"
		"			colorConverted[idx] = 12.92f * colorBlended[idx]; \n"
		"		} \n"
		"		else \n"
		"		{ \n"
		"			colorConverted[idx] = 1.055f * pow(colorBlended[idx], 0.41666f) - 0.055f; \n"
		"		} \n"
		"	} \n"
		"	return colorConverted; \n"
		"} \n";

	TestFunction testFunction(functionName, functionDefinition);

	return testFunction;
}

struct FBOConfig
{
	FBOConfig					(const deUint32 textureInternalFormatValue,
								 const tcu::Vec4 textureColorValue,
								 const deUint32 fboTargetTypeValue,
								 const deUint32 fboColorAttachmentValue,
								 const FboType fboTypeValue)
		: textureInternalFormat	(textureInternalFormatValue)
		, textureColor			(textureColorValue)
		, fboTargetType			(fboTargetTypeValue)
		, fboColorAttachment	(fboColorAttachmentValue)
		, fboType				(fboTypeValue) {}
	~FBOConfig					(void) {}

	deUint32	textureInternalFormat;
	tcu::Vec4	textureColor;
	deUint32	fboTargetType;
	deUint32	fboColorAttachment;
	FboType		fboType;
};

struct BlendConfig
{
	deUint32	equation;
	deUint32	funcSrc;
	deUint32	funcDst;
};

std::vector<BlendConfig> getBlendingConfigList (void)
{
	BlendConfig blendConfigs[12];

	// add function permutations
	blendConfigs[0].equation = GL_FUNC_ADD;
	blendConfigs[1].equation = GL_FUNC_ADD;
	blendConfigs[2].equation = GL_FUNC_ADD;
	blendConfigs[3].equation = GL_FUNC_ADD;

	blendConfigs[0].funcSrc = GL_ONE;
	blendConfigs[0].funcDst = GL_ONE;
	blendConfigs[1].funcSrc = GL_ONE;
	blendConfigs[1].funcDst = GL_ZERO;
	blendConfigs[2].funcSrc = GL_ZERO;
	blendConfigs[2].funcDst = GL_ONE;
	blendConfigs[3].funcSrc = GL_ZERO;
	blendConfigs[3].funcDst = GL_ZERO;

	// subtract function permutations
	blendConfigs[4].equation = GL_FUNC_SUBTRACT;
	blendConfigs[5].equation = GL_FUNC_SUBTRACT;
	blendConfigs[6].equation = GL_FUNC_SUBTRACT;
	blendConfigs[7].equation = GL_FUNC_SUBTRACT;

	blendConfigs[4].funcSrc = GL_ONE;
	blendConfigs[4].funcDst = GL_ONE;
	blendConfigs[5].funcSrc = GL_ONE;
	blendConfigs[5].funcDst = GL_ZERO;
	blendConfigs[6].funcSrc = GL_ZERO;
	blendConfigs[6].funcDst = GL_ONE;
	blendConfigs[7].funcSrc = GL_ZERO;
	blendConfigs[7].funcDst = GL_ZERO;

	// reverse subtract function permutations
	blendConfigs[8].equation = GL_FUNC_REVERSE_SUBTRACT;
	blendConfigs[9].equation = GL_FUNC_REVERSE_SUBTRACT;
	blendConfigs[10].equation = GL_FUNC_REVERSE_SUBTRACT;
	blendConfigs[11].equation = GL_FUNC_REVERSE_SUBTRACT;

	blendConfigs[8].funcSrc = GL_ONE;
	blendConfigs[8].funcDst = GL_ONE;
	blendConfigs[9].funcSrc = GL_ONE;
	blendConfigs[9].funcDst = GL_ZERO;
	blendConfigs[10].funcSrc = GL_ZERO;
	blendConfigs[10].funcDst = GL_ONE;
	blendConfigs[11].funcSrc = GL_ZERO;
	blendConfigs[11].funcDst = GL_ZERO;

	std::vector<BlendConfig> configList(blendConfigs, blendConfigs + DE_LENGTH_OF_ARRAY(blendConfigs));

	return configList;
}

struct TestRenderPassConfig
{
	TestRenderPassConfig		(void)
		: testFunction			(false) {}

	TestRenderPassConfig		(const TextureSourcesType textureSourcesTypeValue,
								FBOConfig fboConfigListValue,
								const FramebufferSRGB framebufferSRGBValue,
								const FramebufferBlend framebufferBendValue,
								const RendererTask rendererTaskValue)
		: textureSourcesType	(textureSourcesTypeValue)
		, framebufferSRGB		(framebufferSRGBValue)
		, frameBufferBlend		(framebufferBendValue)
		, testFunction			(false)
		, rendererTask			(rendererTaskValue) {fboConfigList.push_back(fboConfigListValue);}

	TestRenderPassConfig		(const TextureSourcesType textureSourcesTypeValue,
								FBOConfig fboConfigListValue,
								const FramebufferSRGB framebufferSRGBValue,
								const FramebufferBlend framebufferBendValue,
								TestFunction testFunctionValue,
								const RendererTask rendererTaskValue)
		: textureSourcesType	(textureSourcesTypeValue)
		, framebufferSRGB		(framebufferSRGBValue)
		, frameBufferBlend		(framebufferBendValue)
		, testFunction			(testFunctionValue)
		, rendererTask			(rendererTaskValue) {fboConfigList.push_back(fboConfigListValue);}

	TestRenderPassConfig		(const TextureSourcesType textureSourcesTypeValue,
								std::vector<FBOConfig> fboConfigListValue,
								const FramebufferSRGB framebufferSRGBValue,
								const FramebufferBlend framebufferBendValue,
								TestFunction testFunctionValue,
								const RendererTask rendererTaskValue)
		: textureSourcesType	(textureSourcesTypeValue)
		, fboConfigList			(fboConfigListValue)
		, framebufferSRGB		(framebufferSRGBValue)
		, frameBufferBlend		(framebufferBendValue)
		, testFunction			(testFunctionValue)
		, rendererTask			(rendererTaskValue) {}

	~TestRenderPassConfig		(void) {}

	TextureSourcesType		textureSourcesType;
	std::vector<FBOConfig>	fboConfigList;
	FramebufferSRGB			framebufferSRGB;
	FramebufferBlend		frameBufferBlend;
	TestFunction			testFunction;
	RendererTask			rendererTask;
};

class TestVertexData
{
public:
							TestVertexData		(Context& context);
							~TestVertexData		(void);

	void					init				(void);

	void					bind				(void) const;
	void					unbind				(void) const;

private:
	const glw::Functions*	m_gl;
	std::vector<float>		m_data;
	glw::GLuint				m_vboHandle;
	glw::GLuint				m_vaoHandle;
};

TestVertexData::TestVertexData	(Context& context)
	: m_gl						(&context.getRenderContext().getFunctions())
{
	const glw::GLfloat		vertexData[]	=
	{
		// position				// texcoord
		-1.0f, -1.0f, 0.0f,		0.0f, 0.0f, // bottom left corner
		 1.0f, -1.0f, 0.0f,		1.0f, 0.0f, // bottom right corner
		 1.0f,  1.0f, 0.0f,		1.0f, 1.0f, // Top right corner

		-1.0f,  1.0f, 0.0f,		0.0f, 1.0f, // top left corner
		 1.0f,  1.0f, 0.0f,		1.0f, 1.0f, // Top right corner
		-1.0f, -1.0f, 0.0f,		0.0f, 0.0f  // bottom left corner
	};

	m_data.resize(DE_LENGTH_OF_ARRAY(vertexData));
	for (int idx = 0; idx < (int)m_data.size(); idx++)
		m_data[idx] = vertexData[idx];

	m_gl->genVertexArrays(1, &m_vaoHandle);
	m_gl->bindVertexArray(m_vaoHandle);

	m_gl->genBuffers(1, &m_vboHandle);
	m_gl->bindBuffer(GL_ARRAY_BUFFER, m_vboHandle);

	m_gl->bufferData(GL_ARRAY_BUFFER, (glw::GLsizei)(m_data.size() * sizeof(glw::GLfloat)), &m_data[0], GL_STATIC_DRAW);

	m_gl->enableVertexAttribArray(0);
	m_gl->vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * (glw::GLsizei)sizeof(float), (glw::GLvoid *)0);
	m_gl->enableVertexAttribArray(1);
	m_gl->vertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * (glw::GLsizei)sizeof(float), (glw::GLvoid *)(3 * sizeof(float)));

	m_gl->bindVertexArray(0);
	m_gl->bindBuffer(GL_ARRAY_BUFFER, 0);
	GLU_EXPECT_NO_ERROR(m_gl->getError(), "gl error during vertex data setup");
}

TestVertexData::~TestVertexData (void)
{
	m_gl->deleteBuffers(1, &m_vboHandle);
	m_gl->deleteVertexArrays(1, &m_vaoHandle);
}

void TestVertexData::bind (void) const
{
	m_gl->bindVertexArray(m_vaoHandle);
}

void TestVertexData::unbind (void) const
{
	m_gl->bindVertexArray(0);
}

class TestTexture2D
{
public:
								TestTexture2D		(Context& context, const deUint32 internalFormatValue, const deUint32 transferFormatValue, const deUint32 transferTypeValue, const tcu::Vec4 imageColorValue);
								~TestTexture2D		(void);

	int							getTextureUnit		(void) const;
	deUint32					getHandle			(void) const;

	void						bind				(const int textureUnit);
	void						unbind				(void) const;

private:
	const glw::Functions*		m_gl;
	glw::GLuint					m_handle;
	const deUint32				m_internalFormat;
	tcu::TextureFormat			m_transferFormat;
	int							m_width;
	int							m_height;
	tcu::TextureLevel			m_imageData;
	int							m_textureUnit;
};

TestTexture2D::TestTexture2D	(Context& context, const deUint32 internalFormat, const deUint32 transferFormat, const deUint32 transferType, const tcu::Vec4 imageColor)
	: m_gl						(&context.getRenderContext().getFunctions())
	, m_internalFormat			(internalFormat)
	, m_transferFormat			(tcu::TextureFormat(glu::mapGLTransferFormat(transferFormat, transferType)))
	, m_width					(TestTextureSizes::WIDTH)
	, m_height					(TestTextureSizes::HEIGHT)
	, m_imageData				(tcu::TextureLevel(glu::mapGLInternalFormat(internalFormat), m_width, m_height, 1))
{
	// fill image data with a solid test color
	tcu::clear(m_imageData.getAccess(), tcu::Vec4(0.0f));
	for (int py = 0; py < m_imageData.getHeight(); py++)
	{
		for (int px = 0; px < m_imageData.getWidth(); px++)
			m_imageData.getAccess().setPixel(imageColor, px, py);
	}

	m_gl->genTextures(1, &m_handle);

	m_gl->bindTexture(GL_TEXTURE_2D, m_handle);
	m_gl->texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_S,		GL_MIRRORED_REPEAT);
	m_gl->texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_WRAP_T,		GL_MIRRORED_REPEAT);
	m_gl->texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
	m_gl->texParameteri(GL_TEXTURE_2D,	GL_TEXTURE_MAG_FILTER,	GL_NEAREST);

	m_gl->texImage2D(GL_TEXTURE_2D, 0, m_internalFormat, m_width, m_height, 0, transferFormat, transferType, m_imageData.getAccess().getDataPtr());

	m_gl->bindTexture(GL_TEXTURE_2D, 0);
}

TestTexture2D::~TestTexture2D (void)
{
	m_gl->deleteTextures(1, &m_handle);
}

int TestTexture2D::getTextureUnit (void) const
{
	return m_textureUnit;
}

deUint32 TestTexture2D::getHandle (void) const
{
	return m_handle;
}

void TestTexture2D::bind (const int textureUnit)
{
	m_textureUnit = textureUnit;
	m_gl->activeTexture(GL_TEXTURE0 + m_textureUnit);
	m_gl->bindTexture(GL_TEXTURE_2D, m_handle);
}

void TestTexture2D::unbind (void) const
{
	m_gl->bindTexture(GL_TEXTURE_2D, 0);
}

class TestFramebuffer
{
public:
												TestFramebuffer			(void);
												TestFramebuffer			(Context& context, const deUint32 targetType, const deUint32 colorAttachment, glw::GLuint textureAttachmentHandle, const bool isSRGB, const FboType fboType, const int idx);
												~TestFramebuffer		(void);

	void										setTargetType			(const deUint32 targetType);

	FboType										getType					(void) const;
	deUint32									getColorAttachment		(void) const;
	int											getIdx					(void) const;

	void										bind					(void);
	void										unbind					(void);

	typedef de::UniquePtr<glu::Framebuffer>		fboUniquePtr;

private:
	const glw::Functions*						m_gl;
	fboUniquePtr								m_referenceSource;
	deUint32									m_targetType;
	bool										m_bound;
	bool										m_isSRGB;
	FboType										m_type;
	const int									m_idx;
	deUint32									m_colorAttachment;
};

TestFramebuffer::TestFramebuffer	(Context& context, const deUint32 targetType, const deUint32 colorAttachment, glw::GLuint textureAttachmentHandle, const bool isSRGB, const FboType fboType, const int idx)
	: m_gl							(&context.getRenderContext().getFunctions())
	, m_referenceSource				(new glu::Framebuffer(context.getRenderContext()))
	, m_targetType					(targetType)
	, m_bound						(false)
	, m_isSRGB						(isSRGB)
	, m_type						(fboType)
	, m_idx							(idx)
	, m_colorAttachment				(colorAttachment)
{
	m_gl->bindFramebuffer(m_targetType, **m_referenceSource);

	m_gl->framebufferTexture2D(m_targetType, m_colorAttachment, GL_TEXTURE_2D, textureAttachmentHandle, 0);

	TCU_CHECK(m_gl->checkFramebufferStatus(m_targetType) == GL_FRAMEBUFFER_COMPLETE);

	if (targetType == GL_DRAW_BUFFER)
	{
		glw::GLuint textureAttachments[] = {m_colorAttachment};
		m_gl->drawBuffers(DE_LENGTH_OF_ARRAY(textureAttachments), textureAttachments);
		GLU_EXPECT_NO_ERROR(m_gl->getError(), "glDrawBuffer()");
	}

	if (targetType == GL_READ_BUFFER)
	{
		m_gl->readBuffer(m_colorAttachment);
		GLU_EXPECT_NO_ERROR(m_gl->getError(), "glReadBuffer()");
	}

	m_gl->bindFramebuffer(m_targetType, 0);
}

TestFramebuffer::~TestFramebuffer (void)
{
}

void TestFramebuffer::setTargetType (const deUint32 targetType)
{
	m_targetType = targetType;
}

FboType TestFramebuffer::getType (void) const
{
	return m_type;
}

deUint32 TestFramebuffer::getColorAttachment (void) const
{
	return m_colorAttachment;
}

int TestFramebuffer::getIdx (void) const
{
	return m_idx;
}

void TestFramebuffer::bind (void)
{
	if (!m_bound)
	{
		m_gl->bindFramebuffer(m_targetType, **m_referenceSource);
		m_bound = true;
	}
}

void TestFramebuffer::unbind (void)
{
	if (m_bound)
	{
		m_gl->bindFramebuffer(m_targetType, 0);
		m_bound = false;
	}
}

class TestShaderProgram
{
public:
										TestShaderProgram		(Context& context, const int samplerTotal, TestFunction testFunction);
										~TestShaderProgram		(void);

	glw::GLuint							getHandle				(void) const;

	void								use						(void) const;
	void								unuse					(void) const;

	glu::ShaderProgramInfo				getLogInfo				(void);

private:
	const glw::Functions*				m_gl;
	de::MovePtr<glu::ShaderProgram>		m_referenceSource;
	const int							m_samplerTotal;
	const int							m_shaderStagesTotal;
};

TestShaderProgram::TestShaderProgram	(Context& context, const int samplerTotal, TestFunction testFunction)
	: m_gl								(&context.getRenderContext().getFunctions())
	, m_samplerTotal					(samplerTotal)
	, m_shaderStagesTotal				(2)
{
	std::ostringstream		shaderFragment;

	const char* const shaderVertex =
		"#version 310 es \n"
		"layout (location = 0) in mediump vec3 aPosition; \n"
		"layout (location = 1) in mediump vec2 aTexCoord; \n"
		"out mediump vec2 vs_aTexCoord; \n"
		"void main () \n"
		"{ \n"
		"	gl_Position = vec4(aPosition, 1.0f); \n"
		"	vs_aTexCoord = aTexCoord; \n"
		"} \n";

	shaderFragment
		<< "#version 310 es \n"
		<< "in mediump vec2 vs_aTexCoord; \n"
		<< "layout (location = 0) out mediump vec4 fs_aColor0; \n";

	for (int samplerIdx = 0; samplerIdx < m_samplerTotal; samplerIdx++)
		shaderFragment
			<< "uniform sampler2D uTexture" << samplerIdx << "; \n";

	shaderFragment
		<< "uniform int uFunctionType; \n";

	if (testFunction.hasFunction)
		shaderFragment
		<< "uniform int uBlendFunctionType; \n"
		<< "uniform mediump float uFactorSrc; \n"
		<< "uniform mediump float uFactorDst; \n"
			<< testFunction.functionDefintion;

	shaderFragment
		<< "void main () \n"
		<< "{ \n";

	for (int samplerIdx = 0; samplerIdx < m_samplerTotal; samplerIdx++)
		shaderFragment
			<<"	mediump vec4 texelColor" << samplerIdx << " = vec4(0.0f, 0.0f, 0.0f, 1.0f); \n";

	shaderFragment
		<< buildSamplingPassType(m_samplerTotal);

	if (testFunction.hasFunction)
		shaderFragment
			<< "	fs_aColor0 = " << testFunction.functionName << "(texelColor0, texelColor1); \n";
	else
		shaderFragment
			<< "	fs_aColor0 = texelColor0; \n";

	shaderFragment
		<< "} \n";

	m_referenceSource = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(context.getRenderContext(), glu::makeVtxFragSources(shaderVertex, shaderFragment.str())));
	if (!m_referenceSource->isOk())
	{
		tcu::TestLog& log = context.getTestContext().getLog();
		log << this->getLogInfo();
		TCU_FAIL("Failed to compile shaders and link program");
	}
}

TestShaderProgram::~TestShaderProgram (void)
{
	m_referenceSource = de::MovePtr<glu::ShaderProgram>(DE_NULL);
	m_referenceSource.clear();
}

deUint32 TestShaderProgram::getHandle (void) const
{
	return m_referenceSource->getProgram();
}

void TestShaderProgram::use (void) const
{
	m_gl->useProgram(this->getHandle());
}

void TestShaderProgram::unuse (void) const
{
	m_gl->useProgram(0);
}

glu::ShaderProgramInfo TestShaderProgram::getLogInfo (void)
{
	glu::ShaderProgramInfo	buildInfo;

	// log shader program info. Only vertex and fragment shaders included
	buildInfo.program = m_referenceSource->getProgramInfo();
	for (int shaderIdx = 0; shaderIdx < m_shaderStagesTotal; shaderIdx++)
	{
		glu::ShaderInfo shaderInfo = m_referenceSource->getShaderInfo(static_cast<glu::ShaderType>(static_cast<int>(glu::SHADERTYPE_VERTEX) + static_cast<int>(shaderIdx)), 0);
		buildInfo.shaders.push_back(shaderInfo);
	}
	return buildInfo;
}

class Renderer
{
public:
											Renderer						(Context& context);
											~Renderer						(void);

	void									init							(const TestRenderPassConfig& renderPassConfig, const int renderpass);
	void									deinit							(void);

	void									setSamplingType					(const SamplingType samplerIdx);
	void									setBlendIteration				(const int blendIteration);
	void									setFramebufferBlend				(const bool blend);
	void									setFramebufferSRGB				(const bool sRGB);

	std::vector<tcu::Vec4>					getResultsPreDraw				(void) const;
	std::vector<tcu::Vec4>					getResultsPostDraw				(void) const;
	int										getBlendConfigCount				(void) const;
	glu::ShaderProgramInfo					getShaderProgramInfo			(void);

	void									copyFrameBufferTexture			(const int srcPx, const int srcPy, const int dstPx, const int dstPy);
	void									draw							(void);
	void									storeShaderProgramInfo			(void);
	void									logShaderProgramInfo			(void);

	typedef de::SharedPtr<TestTexture2D>	TextureSp;
	typedef de::SharedPtr<TestFramebuffer>	FboSp;

private:
	void									createFBOwithColorAttachment	(const std::vector<FBOConfig> fboConfigList);
	void									setShaderProgramSamplingType	(const int samplerIdx);
	void									setShaderBlendFunctionType		(void);
	void									setShaderBlendSrcDstValues		(void);
	void									bindActiveTexturesSamplers		(void);
	void									bindAllRequiredSourceTextures	(const TextureSourcesType texturesRequired);
	void									unbindAllSourceTextures			(void);
	void									bindFramebuffer					(const int framebufferIdx);
	void									unbindFramebuffer				(const int framebufferIdx);
	void									enableFramebufferSRGB			(void);
	void									enableFramebufferBlend			(void);
	bool									isFramebufferAttachmentSRGB		(const deUint32 targetType, const deUint32 attachment) const;
	void									readTexels						(const int px, const int py, const deUint32 attachment, tcu::Vec4& texelData);
	void									logState						(const deUint32 targetType, const deUint32 attachment, const SamplingType samplingType) const;

	// renderer specific constants initialized during constructor
	Context&								m_context;
	const TestVertexData					m_vertexData;
	const int								m_textureSourceTotal;

	// additional resources monitored by the renderer
	std::vector<BlendConfig>				m_blendConfigList;
	std::vector<TextureSp>					m_textureSourceList;
	TestRenderPassConfig					m_renderPassConfig;
	std::vector<TextureSp>					m_fboTextureList;
	TestShaderProgram*						m_shaderProgram;
	std::vector<FboSp>						m_framebufferList;
	std::vector<tcu::Vec4>					m_resultsListPreDraw;
	std::vector<tcu::Vec4>					m_resultsListPostDraw;

	// mutable state variables (internal access only)
	bool									m_hasShaderProgramInfo;
	int										m_renderPass;
	int										m_samplersRequired;
	bool									m_hasFunction;
	bool									m_blittingEnabled;
	glu::ShaderProgramInfo					m_shaderProgramInfo;

	// mutable state variables (external access via setters)
	SamplingType							m_samplingType;
	int										m_blendIteration;
	bool									m_framebufferBlendEnabled;
	bool									m_framebufferSRGBEnabled;
};

Renderer::Renderer				(Context& context)
	: m_context					(context)
	, m_vertexData				(context)
	, m_textureSourceTotal		(2)
	, m_blendConfigList			(getBlendingConfigList())
	, m_hasShaderProgramInfo	(false)
{
	TextureSp textureLinear(new TestTexture2D(m_context, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, getTestColorLinear()));
	m_textureSourceList.push_back(textureLinear);

	TextureSp textureSRGB(new TestTexture2D(m_context, GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, getTestColorLinear()));
	m_textureSourceList.push_back(textureSRGB);
}

Renderer::~Renderer (void)
{
	m_textureSourceList.clear();
	this->deinit();
}

void Renderer::init (const TestRenderPassConfig& renderPassConfig, const int renderpass)
{
	m_renderPassConfig = renderPassConfig;
	m_renderPass = renderpass;

	this->createFBOwithColorAttachment(m_renderPassConfig.fboConfigList);

	if (m_renderPassConfig.textureSourcesType != TEXTURESOURCESTYPE_NONE)
	{
		if (m_renderPassConfig.textureSourcesType == TEXTURESOURCESTYPE_RGBA || m_renderPassConfig.textureSourcesType == TEXTURESOURCESTYPE_SRGBA)
			m_samplersRequired = 1;
		else if (m_renderPassConfig.textureSourcesType ==TEXTURESOURCESTYPE_BOTH )
			m_samplersRequired = 2;
		else
			DE_FATAL("Error: Texture source required not recognised");

		m_shaderProgram = new TestShaderProgram(m_context, m_samplersRequired, m_renderPassConfig.testFunction);
		m_hasFunction = m_renderPassConfig.testFunction.hasFunction;
	}
	else
		m_shaderProgram = DE_NULL;
}

void Renderer::deinit (void)
{
	if (m_shaderProgram != DE_NULL)
	{
		delete m_shaderProgram;
		m_shaderProgram = DE_NULL;
	}

	m_fboTextureList.clear();
	m_framebufferList.clear();
}

void Renderer::setSamplingType (const SamplingType samplingType)
{
	m_samplingType = samplingType;
}

void Renderer::setBlendIteration (const int blendIteration)
{
	m_blendIteration = blendIteration;
}

void Renderer::setFramebufferBlend (const bool blend)
{
	m_framebufferBlendEnabled = blend;
}

void Renderer::setFramebufferSRGB (const bool sRGB)
{
	m_framebufferSRGBEnabled = sRGB;
}

std::vector<tcu::Vec4> Renderer::getResultsPreDraw (void) const
{
	return m_resultsListPreDraw;
}

std::vector<tcu::Vec4> Renderer::getResultsPostDraw (void) const
{
	return m_resultsListPostDraw;
}

int Renderer::getBlendConfigCount (void) const
{
	return (int)m_blendConfigList.size();
}

void Renderer::copyFrameBufferTexture (const int srcPx, const int srcPy, const int dstPx, const int dstPy)
{
	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
	int						fboSrcIdx				= -1;
	int						fboDstIdx				= -1;
	deUint32				fboSrcColAttachment		= GL_NONE;
	deUint32				fboDstColAttachment		= GL_NONE;

	for (int idx = 0; idx < (int)m_framebufferList.size(); idx++)
		this->bindFramebuffer(idx);

	// cache fbo attachments and idx locations
	for (int idx = 0; idx < (int)m_framebufferList.size(); idx++)
	{
		if (m_framebufferList[idx]->getType() == FBOTYPE_SOURCE)
		{
			fboSrcIdx = m_framebufferList[idx]->getIdx();
			fboSrcColAttachment = m_framebufferList[fboSrcIdx]->getColorAttachment();
		}
		if (m_framebufferList[idx]->getType() == FBOTYPE_DESTINATION)
		{
			fboDstIdx = m_framebufferList[idx]->getIdx();
			fboDstColAttachment = m_framebufferList[fboDstIdx]->getColorAttachment();
		}
	}

	for (int idx = 0; idx < (int)m_framebufferList.size(); idx++)
		m_framebufferList[idx]->unbind();

	// store texel data from both src and dst before performing the copy
	m_resultsListPreDraw.resize(2);
	m_framebufferList[fboSrcIdx]->bind();
	this->readTexels(0, 0, fboSrcColAttachment, m_resultsListPreDraw[0]);
	m_framebufferList[fboSrcIdx]->unbind();
	m_framebufferList[fboDstIdx]->setTargetType(GL_READ_FRAMEBUFFER);
	m_framebufferList[fboDstIdx]->bind();
	this->readTexels(0, 0, fboDstColAttachment, m_resultsListPreDraw[1]);
	m_framebufferList[fboDstIdx]->unbind();
	m_framebufferList[fboDstIdx]->setTargetType(GL_DRAW_FRAMEBUFFER);

	m_framebufferList[fboSrcIdx]->bind();
	m_framebufferList[fboDstIdx]->bind();

	this->enableFramebufferSRGB();
	this->enableFramebufferBlend();

	gl.blitFramebuffer(	srcPx, srcPy, TestTextureSizes::WIDTH, TestTextureSizes::HEIGHT,
						dstPx, dstPy, TestTextureSizes::WIDTH, TestTextureSizes::HEIGHT,
						GL_COLOR_BUFFER_BIT, GL_NEAREST);

	m_resultsListPostDraw.resize(2);
	this->readTexels(0, 0, fboSrcColAttachment, m_resultsListPostDraw[0]);
	m_framebufferList[fboSrcIdx]->unbind();
	m_framebufferList[fboDstIdx]->unbind();

	m_framebufferList[fboDstIdx]->setTargetType(GL_READ_FRAMEBUFFER);
	m_framebufferList[fboDstIdx]->bind();
	this->readTexels(0, 0, fboDstColAttachment, m_resultsListPostDraw[1]);
	m_framebufferList[fboDstIdx]->unbind();
}

void Renderer::draw (void)
{
	const glw::Functions&	gl = m_context.getRenderContext().getFunctions();

	if (m_renderPassConfig.textureSourcesType == TEXTURESOURCESTYPE_NONE)
		DE_FATAL("Error: Attempted to draw with no texture sources");

	// resize results storage with each render pass
	m_resultsListPreDraw.resize(m_renderPass + 1);
	m_resultsListPostDraw.resize(m_renderPass + 1);

	m_shaderProgram->use();
	m_vertexData.bind();

	for (int idx = 0; idx < (int)m_framebufferList.size(); idx++)
		this->bindFramebuffer(idx);

	this->bindAllRequiredSourceTextures(m_renderPassConfig.textureSourcesType);
	this->bindActiveTexturesSamplers();

	this->enableFramebufferSRGB();
	this->enableFramebufferBlend();

	this->readTexels(0, 0, GL_COLOR_ATTACHMENT0, m_resultsListPreDraw[m_renderPass]);
	this->setShaderProgramSamplingType(m_samplingType);
	if (m_hasFunction)
	{
		this->setShaderBlendFunctionType();
		this->setShaderBlendSrcDstValues();
	}

	gl.drawArrays(GL_TRIANGLES, 0, 6);

	this->readTexels(0, 0, GL_COLOR_ATTACHMENT0, m_resultsListPostDraw[m_renderPass]);
	this->logState(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_samplingType);

	this->unbindAllSourceTextures();
	for (int idx = 0; idx < (int)m_framebufferList.size(); idx++)
		this->unbindFramebuffer(idx);
	m_vertexData.unbind();
	m_shaderProgram->unuse();
}

void Renderer::storeShaderProgramInfo (void)
{
	m_shaderProgramInfo = m_shaderProgram->getLogInfo();
	m_hasShaderProgramInfo = true;
}

void Renderer::logShaderProgramInfo (void)
{
	tcu::TestLog& log = m_context.getTestContext().getLog();

	if (m_hasShaderProgramInfo)
		log << m_shaderProgramInfo;
}

void Renderer::createFBOwithColorAttachment (const std::vector<FBOConfig> fboConfigList)
{
	const int size = (int)fboConfigList.size();
	for (int idx = 0; idx < size; idx++)
	{
		TextureSp texture(new TestTexture2D(m_context, fboConfigList[idx].textureInternalFormat, GL_RGBA, GL_UNSIGNED_BYTE, fboConfigList[idx].textureColor));
		m_fboTextureList.push_back(texture);

		bool isSRGB;
		if (fboConfigList[idx].textureInternalFormat == GL_SRGB8_ALPHA8)
			isSRGB = true;
		else
			isSRGB = false;

		FboSp framebuffer(new TestFramebuffer(m_context, fboConfigList[idx].fboTargetType, fboConfigList[idx].fboColorAttachment, texture->getHandle(), isSRGB, fboConfigList[idx].fboType, idx));
		m_framebufferList.push_back(framebuffer);
	}
}

void Renderer::setShaderProgramSamplingType (const int samplerIdx)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	glw::GLuint location = gl.getUniformLocation(m_shaderProgram->getHandle(), "uFunctionType");
	DE_ASSERT(location != (glw::GLuint)-1);
	gl.uniform1i(location, samplerIdx);
}

void Renderer::setShaderBlendFunctionType (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	int function = -1;
	if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_ADD)
		function = 0;
	else if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_SUBTRACT)
		function = 1;
	else if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_REVERSE_SUBTRACT)
		function = 2;
	else
		DE_FATAL("Error: Blend function not recognised");

	glw::GLuint location = gl.getUniformLocation(m_shaderProgram->getHandle(), "uBlendFunctionType");
	DE_ASSERT(location != (glw::GLuint)-1);
	gl.uniform1i(location, function);
}

void Renderer::setShaderBlendSrcDstValues (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	float funcSrc;
	if (m_blendConfigList[m_blendIteration].funcSrc == GL_ONE)
		funcSrc = 1.0f;
	else
		funcSrc = 0.0f;

	float funcDst;
		if (m_blendConfigList[m_blendIteration].funcDst == GL_ONE)
		funcDst = 1.0f;
	else
		funcDst = 0.0f;

	glw::GLuint locationSrc = gl.getUniformLocation(m_shaderProgram->getHandle(), "uFactorSrc");
	gl.uniform1f(locationSrc, funcSrc);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f()");

	glw::GLuint locationDst = gl.getUniformLocation(m_shaderProgram->getHandle(), "uFactorDst");
	gl.uniform1f(locationDst, funcDst);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1f()");
}

void Renderer::bindActiveTexturesSamplers (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	for (int idx = 0; idx < m_samplersRequired; idx++)
	{
		std::ostringstream stream;
		stream << "uTexture" << idx;
		std::string uniformName(stream.str());
		glw::GLint location = gl.getUniformLocation(m_shaderProgram->getHandle(), uniformName.c_str());
		DE_ASSERT(location != -1);
		gl.uniform1i(location, m_textureSourceList[idx]->getTextureUnit());
		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetUniformLocation()");
	}
}

void Renderer::bindAllRequiredSourceTextures (const TextureSourcesType texturesRequired)
{
	if (texturesRequired == TEXTURESOURCESTYPE_RGBA)
		m_textureSourceList[0]->bind(0);
	else if (texturesRequired == TEXTURESOURCESTYPE_SRGBA)
		m_textureSourceList[1]->bind(0);
	else if (texturesRequired == TEXTURESOURCESTYPE_BOTH)
	{
		m_textureSourceList[0]->bind(0);
		m_textureSourceList[1]->bind(1);
	}
	else
		DE_FATAL("Error: Invalid sources requested in bind all");
}

void Renderer::unbindAllSourceTextures (void)
{
	for (int idx = 0; idx < (int)m_textureSourceList.size(); idx++)
		m_textureSourceList[idx]->unbind();
}

void Renderer::bindFramebuffer (const int framebufferIdx)
{
	m_framebufferList[framebufferIdx]->bind();
}

void Renderer::unbindFramebuffer (const int framebufferIdx)
{
	m_framebufferList[framebufferIdx]->unbind();
}

void Renderer::enableFramebufferSRGB (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_framebufferSRGBEnabled)
		gl.enable(GL_FRAMEBUFFER_SRGB);
	else
		gl.disable(GL_FRAMEBUFFER_SRGB);
}

void Renderer::enableFramebufferBlend (void)
{
	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
	tcu::TestLog&			log	= m_context.getTestContext().getLog();
	std::ostringstream		message;

	message << "Blend settings = ";

	if (m_framebufferBlendEnabled)
	{
		gl.enable(GL_BLEND);
		gl.blendEquation(m_blendConfigList[m_blendIteration].equation);
		gl.blendFunc(m_blendConfigList[m_blendIteration].funcSrc, m_blendConfigList[m_blendIteration].funcDst);

		std::string equation, src, dst;
		if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_ADD)
			equation = "GL_FUNC_ADD";
		if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_SUBTRACT)
			equation = "GL_FUNC_SUBTRACT";
		if (m_blendConfigList[m_blendIteration].equation == GL_FUNC_REVERSE_SUBTRACT)
			equation = "GL_FUNC_REVERSE_SUBTRACT";
		if (m_blendConfigList[m_blendIteration].funcSrc == GL_ONE)
			src = "GL_ONE";
		else
			src = "GL_ZERO";
		if (m_blendConfigList[m_blendIteration].funcDst == GL_ONE)
			dst = "GL_ONE";
		else
			dst = "GL_ZERO";

		message << "Enabled: equation = " << equation << ", func src = " << src << ", func dst = " << dst;
	}
	else
	{
		gl.disable(GL_BLEND);
		message << "Disabled";
	}

	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}

bool Renderer::isFramebufferAttachmentSRGB (const deUint32 targetType, const deUint32 attachment) const
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	glw::GLint				encodingType;

	gl.getFramebufferAttachmentParameteriv(targetType, attachment, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &encodingType);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetNamedFramebufferAttachmentParameteriv()");

	switch (static_cast<glw::GLenum>(encodingType))
	{
		case GL_SRGB:
		{
			return true;
		}
		case GL_LINEAR:
		{
			return false;
		}
		default:
		{
			DE_FATAL("Error: Color attachment format not recognised");
			return false;
		}
	}
}

void Renderer::readTexels (const int px, const int py, const deUint32 mode, tcu::Vec4& texelData)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	tcu::TextureLevel		textureRead;

	// ensure result sampling coordinates are within range of the result color attachment
	DE_ASSERT((px >= 0) && (px < m_context.getRenderTarget().getWidth()));
	DE_ASSERT((py >= 0) && (py < m_context.getRenderTarget().getHeight()));

	gl.readBuffer(mode);
	textureRead.setStorage(glu::mapGLTransferFormat(GL_RGBA, GL_UNSIGNED_BYTE), TestTextureSizes::WIDTH, TestTextureSizes::HEIGHT);
	glu::readPixels(m_context.getRenderContext(), px, py, textureRead.getAccess());
	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
	texelData = textureRead.getAccess().getPixel(px, py);
}

void Renderer::logState (const deUint32 targetType, const deUint32 attachment, const SamplingType samplingType) const
{
	tcu::TestLog&			log					= m_context.getTestContext().getLog();
	std::ostringstream		message;

	bool fboAttachmentSRGB = this->isFramebufferAttachmentSRGB(targetType, attachment);
	message.str("");
	message << "getFramebufferAttachmentParameteriv() check = ";
	if (fboAttachmentSRGB)
		message << "GL_SRGB";
	else
		message << "GL_LINEAR";
	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;

	message.str("");
	message << "Framebuffer color attachment value BEFORE draw call";
	logColor(m_context, message.str(), m_resultsListPreDraw[m_renderPass]);

	message.str("");
	message << "Framebuffer color attachment value AFTER draw call";
	logColor(m_context, message.str(), m_resultsListPostDraw[m_renderPass]);

	message.str("");
	message << "Sampling type = ";
	std::string type;
	if (samplingType == 0)
		type = "texture()";
	else if (samplingType == 1)
		type = "textureLOD()";
	else if (samplingType == 2)
		type = "textureGrad()";
	else if (samplingType == 3)
		type = "textureOffset()";
	else if (samplingType == 4)
		type = "textureProj()";
	else
		DE_FATAL("Error: Sampling type unregonised");
	message << type;
	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;

	message.str("");
	if (m_framebufferSRGBEnabled)
		message << "Framebuffer SRGB = enabled";
	else
		message << "Framebuffer SRGB = disabled";
	log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}

class FboSRGBTestCase : public TestCase
{
public:
											FboSRGBTestCase				(Context& context, const char* const name, const char* const desc);
											~FboSRGBTestCase			(void);

	void									init						(void);
	void									deinit						(void);
	IterateResult							iterate						(void);

	void									setTestConfig				(std::vector<TestRenderPassConfig> renderPassConfigList);

	virtual void							setupTest					(void) = 0;
	virtual bool							verifyResult				(void) = 0;

protected:
	bool									m_hasTestConfig;
	std::vector<TestRenderPassConfig>		m_renderPassConfigList;
	bool									m_testcaseRequiresBlend;
	std::vector<tcu::Vec4>					m_resultsPreDraw;
	std::vector<tcu::Vec4>					m_resultsPostDraw;

private:
											FboSRGBTestCase				(const FboSRGBTestCase&);
	FboSRGBTestCase&						operator=					(const FboSRGBTestCase&);
};

FboSRGBTestCase::FboSRGBTestCase	(Context& context, const char* const name, const char* const desc)
	: TestCase						(context, name, desc)
	, m_hasTestConfig				(false)
	, m_testcaseRequiresBlend		(false)
{
}

FboSRGBTestCase::~FboSRGBTestCase (void)
{
	FboSRGBTestCase::deinit();
}

void FboSRGBTestCase::init (void)
{
	if (glu::isContextTypeES(m_context.getRenderContext().getType()))
	{
		// extensions requirements for test
		if (!glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
			TCU_THROW(NotSupportedError, "Test requires a context version equal or higher than 3.2");

		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_sRGB_write_control"))
			TCU_THROW(NotSupportedError, "Test requires extension GL_EXT_sRGB_write_control");

		if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_decode"))
			TCU_THROW(NotSupportedError, "Test requires GL_EXT_texture_sRGB_decode extension");
	}
}

void FboSRGBTestCase::deinit (void)
{
}

FboSRGBTestCase::IterateResult FboSRGBTestCase::iterate (void)
{
	this->setupTest();

	DE_ASSERT(m_hasTestConfig && "Error: Renderer was not supplied a test config");

	Renderer renderer(m_context);

	// loop through each sampling type
	for (int samplingIdx = 0; samplingIdx < SampligTypeCount::MAX; samplingIdx++)
	{
		renderer.setSamplingType(static_cast<SamplingType>(samplingIdx));

		// loop through each blend configuration
		const int blendCount = renderer.getBlendConfigCount();
		for (int blendIdx = 0; blendIdx < blendCount; blendIdx++)
		{
			// loop through each render pass
			const int renderPassCount = (int)m_renderPassConfigList.size();
			for (int renderPassIdx = 0; renderPassIdx < renderPassCount; renderPassIdx++)
			{
				TestRenderPassConfig renderPassConfig = m_renderPassConfigList[renderPassIdx];

				renderer.init(renderPassConfig, renderPassIdx);

				if (blendIdx == 0 && renderPassConfig.rendererTask == RENDERERTASK_DRAW)
					renderer.storeShaderProgramInfo();

				if (renderPassConfig.frameBufferBlend == FRAMEBUFFERBLEND_ENABLED)
				{
					renderer.setBlendIteration(blendIdx);
					renderer.setFramebufferBlend(true);
				}
				else
					renderer.setFramebufferBlend(false);

				if (renderPassConfig.framebufferSRGB == FRAMEBUFFERSRGB_ENABLED)
					renderer.setFramebufferSRGB(true);
				else
					renderer.setFramebufferSRGB(false);

				if (renderPassConfig.rendererTask == RENDERERTASK_DRAW)
					renderer.draw();
				else if (renderPassConfig.rendererTask == RENDERERTASK_COPY)
					renderer.copyFrameBufferTexture(0, 0, 0, 0);
				else
					DE_FATAL("Error: render task not recognised");

				renderer.deinit();

			} // render passes

			m_resultsPreDraw = renderer.getResultsPreDraw();
			m_resultsPostDraw = renderer.getResultsPostDraw();

			bool testPassed = this->verifyResult();
			if (testPassed)
				m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
			else
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
				renderer.logShaderProgramInfo();
				return STOP;
			}

			if (!m_testcaseRequiresBlend)
				break;
		} // blend configs

		renderer.logShaderProgramInfo();
	} // sampling types

	return STOP;
}

void FboSRGBTestCase::setTestConfig (std::vector<TestRenderPassConfig> renderPassConfigList)
{
	m_renderPassConfigList = renderPassConfigList;
	m_hasTestConfig = true;

	for (int idx = 0; idx < (int)renderPassConfigList.size(); idx++)
	{
		if (renderPassConfigList[idx].frameBufferBlend == FRAMEBUFFERBLEND_ENABLED)
		{
			m_testcaseRequiresBlend = true;
			return;
		}
	}
	m_testcaseRequiresBlend = false;
}

class FboSRGBQueryCase : public TestCase
{
public:
					FboSRGBQueryCase	(Context& context, const char* const name, const char* const description);
					~FboSRGBQueryCase	(void);

	void			init				(void);
	void			deinit				(void);
	IterateResult	iterate				(void);
};

FboSRGBQueryCase::FboSRGBQueryCase	(Context& context, const char* const name, const char* const description)
	: TestCase						(context, name, description)
{
}

FboSRGBQueryCase::~FboSRGBQueryCase (void)
{
	FboSRGBQueryCase::deinit();
}

void FboSRGBQueryCase::init (void)
{
	// extension requirements for test
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_sRGB_write_control"))
		TCU_THROW(NotSupportedError, "Test requires extension GL_EXT_sRGB_write_control");
}

void FboSRGBQueryCase::deinit (void)
{
}

FboSRGBQueryCase::IterateResult FboSRGBQueryCase::iterate (void)
{
	// TEST INFO:
	// API tests which check when querying FRAMEBUFFER_SRGB_EXT capability returns the correct information when using glEnabled() or glDisabled()

	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	tcu::TestLog&			log		= m_context.getTestContext().getLog();
	const char*	const		msgPart	= ", after disabling = ";

	for (int idx = 0; idx < static_cast<int>(QUERYTYPE_LAST); idx++)
	{
		std::ostringstream	message;
		bool				pass		= false;

		message << std::string("Results: After Enabling = ");

		gl.enable(GL_FRAMEBUFFER_SRGB);

		switch (static_cast<QueryType>(idx))
		{
			case QUERYTYPE_ISENABLED:
			{
				glw::GLboolean enabled[2];
				enabled[0] = gl.isEnabled(GL_FRAMEBUFFER_SRGB);
				gl.disable(GL_FRAMEBUFFER_SRGB);
				enabled[1] = gl.isEnabled(GL_FRAMEBUFFER_SRGB);

				message << static_cast<float>(enabled[0]) << msgPart << static_cast<float>(enabled[1]);
				pass = (enabled[0] && !(enabled[1])) ? true : false;
				break;
			}
			case QUERYTYPE_BOOLEAN:
			{
				glw::GLboolean enabled[2];
				gl.getBooleanv(GL_FRAMEBUFFER_SRGB,&enabled[0]);
				gl.disable(GL_FRAMEBUFFER_SRGB);
				gl.getBooleanv(GL_FRAMEBUFFER_SRGB,&enabled[1]);

				message << static_cast<float>(enabled[0]) << msgPart << static_cast<float>(enabled[1]);
				pass = (enabled[0] && !(enabled[1])) ? true : false;
				break;
			}
			case QUERYTYPE_FLOAT:
			{
				glw::GLfloat enabled[2];
				gl.getFloatv(GL_FRAMEBUFFER_SRGB, &enabled[0]);
				gl.disable(GL_FRAMEBUFFER_SRGB);
				gl.getFloatv(GL_FRAMEBUFFER_SRGB, &enabled[1]);

				message << static_cast<float>(enabled[0]) << msgPart << static_cast<float>(enabled[1]);
				pass = ((int)enabled[0] && !((int)enabled[1])) ? true : false;
				break;
			}
			case QUERYTYPE_INT:
			{
				glw::GLint enabled[2];
				gl.getIntegerv(GL_FRAMEBUFFER_SRGB, &enabled[0]);
				gl.disable(GL_FRAMEBUFFER_SRGB);
				gl.getIntegerv(GL_FRAMEBUFFER_SRGB, &enabled[1]);

				message << static_cast<float>(enabled[0]) << msgPart << static_cast<float>(enabled[1]);
				pass = (enabled[0] && !(enabled[1])) ? true : false;
				break;
			}
			case QUERYTYPE_INT64:
			{
				glw::GLint64 enabled[2];
				gl.getInteger64v(GL_FRAMEBUFFER_SRGB, &enabled[0]);
				gl.disable(GL_FRAMEBUFFER_SRGB);
				gl.getInteger64v(GL_FRAMEBUFFER_SRGB, &enabled[1]);

				message << static_cast<float>(enabled[0]) << msgPart << static_cast<float>(enabled[1]);
				pass = (enabled[0] && !(enabled[1])) ? true : false;
				break;
			}
			default:
				DE_FATAL("Error: Datatype not recognised");
		}

		log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;

		if (pass)
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		else
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
			return STOP;
		}
	}
	return STOP;
}

class FboSRGBColAttachCase : public FboSRGBTestCase
{
public:
			FboSRGBColAttachCase	(Context& context, const char* const name, const char* const description)
				: FboSRGBTestCase	(context, name, description) {}
			~FboSRGBColAttachCase	(void) {}

	void	setupTest				(void);
	bool	verifyResult			(void);
};

void FboSRGBColAttachCase::setupTest (void)
{
	// TEST INFO:
	// Check if FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING  set to SRGB and FRAMEBUFFER_SRGB_EXT enabled, destination colors are converted from SRGB to linear
	// before and after blending, finally the result is converted back to SRGB for storage

	// NOTE:
	// if fbo pre-draw color set to linaer, color values get linearlized "twice"
	// (0.2f, 0.3f, 0.4f, 1.0f) when sampled i.e. converted in shader = (0.0331048f, 0.073239f, 0.132868f)
	// resulting in the follolwing blending equation (0.2f, 0.3f, 0.4f 1.0f) + (0.0331048, 0.073239, 0.132868) = (0.521569f, 0.647059f, 0.756863f, 1.0f)

	FBOConfig fboConfig0 = FBOConfig(GL_SRGB8_ALPHA8, getTestColorLinear(), GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_SOURCE);
	FBOConfig fboConfig1 = FBOConfig(GL_RGBA8, getTestColorLinear(), GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_SOURCE);

	const TestRenderPassConfig renderPassConfigs[] =
	{
		TestRenderPassConfig(TEXTURESOURCESTYPE_RGBA, fboConfig0, FRAMEBUFFERSRGB_ENABLED, FRAMEBUFFERBLEND_ENABLED, RENDERERTASK_DRAW),
		TestRenderPassConfig(TEXTURESOURCESTYPE_BOTH, fboConfig1, FRAMEBUFFERSRGB_DISABLED, FRAMEBUFFERBLEND_DISABLED, getFunctionBlendLinearToSRGBCheck(), RENDERERTASK_DRAW)
	};
	std::vector<TestRenderPassConfig> renderPassConfigList(renderPassConfigs, renderPassConfigs + DE_LENGTH_OF_ARRAY(renderPassConfigs));

	this->setTestConfig(renderPassConfigList);
}

bool FboSRGBColAttachCase::verifyResult (void)
{
	if (tcu::boolAll(tcu::lessThan(tcu::abs(m_resultsPostDraw[0] - m_resultsPostDraw[1]), getEpsilonError())) || tcu::boolAll(tcu::equal(m_resultsPostDraw[0], m_resultsPostDraw[1])))
		return true;
	else
		return false;
}

class FboSRGBToggleBlendCase : public FboSRGBTestCase
{
public:
			FboSRGBToggleBlendCase		(Context& context, const char* const name, const char* const description)
				: FboSRGBTestCase		(context, name, description) {}
			~FboSRGBToggleBlendCase		(void) {}

	void	setupTest					(void);
	bool	verifyResult				(void);
};

void FboSRGBToggleBlendCase::setupTest (void)
{
	//	TEST INFO:
	//	Test to check if changing FRAMEBUFFER_SRGB_EXT from enabled to disabled. Enabled should produce SRGB color whilst disabled
	//	should produce linear color. Test conducted with blending disabled.

	FBOConfig fboConfig0 = FBOConfig(GL_SRGB8_ALPHA8, getTestColorLinear(), GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_DESTINATION);

	const TestRenderPassConfig renderPassConfigs[] =
	{
		TestRenderPassConfig(TEXTURESOURCESTYPE_RGBA, fboConfig0, FRAMEBUFFERSRGB_ENABLED,  FRAMEBUFFERBLEND_DISABLED, TestFunction(false), RENDERERTASK_DRAW),
		TestRenderPassConfig(TEXTURESOURCESTYPE_RGBA, fboConfig0, FRAMEBUFFERSRGB_DISABLED, FRAMEBUFFERBLEND_DISABLED, TestFunction(false), RENDERERTASK_DRAW)
	};
	std::vector<TestRenderPassConfig> renderPassConfigList(renderPassConfigs, renderPassConfigs + DE_LENGTH_OF_ARRAY(renderPassConfigs));

	this->setTestConfig(renderPassConfigList);
}

bool FboSRGBToggleBlendCase::verifyResult (void)
{
	if (tcu::boolAny(tcu::greaterThan(tcu::abs(m_resultsPostDraw[0] - m_resultsPostDraw[1]), getEpsilonError())))
		return true;
	else
		return false;
}

class FboSRGBRenderTargetIgnoreCase : public FboSRGBTestCase
{
public:
			FboSRGBRenderTargetIgnoreCase		(Context& context, const char* const name, const char* const description)
				: FboSRGBTestCase				(context, name, description) {}
			~FboSRGBRenderTargetIgnoreCase		(void) {}

	void	setupTest							(void);
	bool	verifyResult						(void);
};

void FboSRGBRenderTargetIgnoreCase::setupTest (void)
{
	// TEST INFO:
	// Check if render targets that are non-RGB ignore the state of GL_FRAMEBUFFER_SRGB_EXT. Rendering to an fbo with non-sRGB color
	// attachment should ignore color space conversion, producing linear color.

	FBOConfig fboConfig0 = FBOConfig(GL_RGBA8, getTestColorBlank(), GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_DESTINATION);

	const TestRenderPassConfig renderPassConfigs[] =
	{
		TestRenderPassConfig(TEXTURESOURCESTYPE_RGBA, fboConfig0, FRAMEBUFFERSRGB_ENABLED,  FRAMEBUFFERBLEND_DISABLED, TestFunction(false), RENDERERTASK_DRAW)

	};
	std::vector<TestRenderPassConfig> renderPassConfigList(renderPassConfigs, renderPassConfigs + DE_LENGTH_OF_ARRAY(renderPassConfigs));

	this->setTestConfig(renderPassConfigList);
}

bool FboSRGBRenderTargetIgnoreCase::verifyResult (void)
{
	if (tcu::boolAll(tcu::lessThan(tcu::abs(m_resultsPostDraw[0] - getTestColorLinear()), getEpsilonError())) || tcu::boolAll(tcu::equal(m_resultsPostDraw[0], getTestColorLinear())))
		return true;
	else
		return false;
}

class FboSRGBCopyToLinearCase : public FboSRGBTestCase
{
public:
			FboSRGBCopyToLinearCase		(Context& context, const char* const name, const char* const description)
				: FboSRGBTestCase		(context, name, description) {}
			~FboSRGBCopyToLinearCase	(void) {}

	void	setupTest					(void);
	bool	verifyResult				(void);
};

void FboSRGBCopyToLinearCase::setupTest (void)
{
	// TEST INFO:
	// Check if copying from an fbo with an sRGB color attachment to an fbo with a linear color attachment with FRAMEBUFFER_EXT enabled results in
	// an sRGB to linear conversion

	FBOConfig fboConfigs[] =
	{
		FBOConfig(GL_SRGB8_ALPHA8, getTestColorSRGB(), GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_SOURCE),
		FBOConfig(GL_RGBA8, getTestColorBlank(), GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, FBOTYPE_DESTINATION)
	};
	std::vector<FBOConfig> fboConfigList(fboConfigs, fboConfigs + DE_LENGTH_OF_ARRAY(fboConfigs));

	const TestRenderPassConfig renderPassConfigs[] =
	{
		TestRenderPassConfig(TEXTURESOURCESTYPE_NONE, fboConfigList, FRAMEBUFFERSRGB_ENABLED,  FRAMEBUFFERBLEND_DISABLED, TestFunction(false), RENDERERTASK_COPY)
	};
	std::vector<TestRenderPassConfig> renderPassConfigList(renderPassConfigs, renderPassConfigs + DE_LENGTH_OF_ARRAY(renderPassConfigs));

	this->setTestConfig(renderPassConfigList);
}

bool FboSRGBCopyToLinearCase::verifyResult (void)
{
	logColor(m_context, "pre-copy source fbo color values", m_resultsPreDraw[0]);
	logColor(m_context, "pre-copy destination fbo color values", m_resultsPreDraw[1]);
	logColor(m_context, "post-copy source fbo color values", m_resultsPostDraw[0]);
	logColor(m_context, "post-copy destination fbo color values", m_resultsPostDraw[1]);

	if (tcu::boolAll(tcu::lessThan(tcu::abs(m_resultsPostDraw[1] - getTestColorLinear()), getEpsilonError())) || tcu::boolAll(tcu::equal(m_resultsPostDraw[1], getTestColorLinear())))
		return true;
	else
		return false;
}

class FboSRGBUnsupportedEnumCase : public TestCase
{
public:
					FboSRGBUnsupportedEnumCase	(Context& context, const char* const name, const char* const description);
					~FboSRGBUnsupportedEnumCase	(void);

	void			init						(void);
	void			deinit						(void);
	bool			isInvalidEnum				(std::string functionName);
	IterateResult	iterate						(void);
};

FboSRGBUnsupportedEnumCase::FboSRGBUnsupportedEnumCase	(Context& context, const char* const name, const char* const description)
	: TestCase						(context, name, description)
{
}

FboSRGBUnsupportedEnumCase::~FboSRGBUnsupportedEnumCase (void)
{
	FboSRGBUnsupportedEnumCase::deinit();
}

void FboSRGBUnsupportedEnumCase::init (void)
{
	if(!glu::isContextTypeES(m_context.getRenderContext().getType()))
		TCU_THROW(NotSupportedError, "The test is not supported in a non-GLES context");

	// extension requirements for test
	if (m_context.getContextInfo().isExtensionSupported("GL_EXT_sRGB_write_control"))
		TCU_THROW(NotSupportedError, "Test requires extension GL_EXT_sRGB_write_control to be unsupported");
}

void FboSRGBUnsupportedEnumCase::deinit (void)
{
}

bool FboSRGBUnsupportedEnumCase::isInvalidEnum (std::string functionName)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	tcu::TestLog&			log			= m_context.getTestContext().getLog();
	bool					isOk		= true;
	glw::GLenum				error		= GL_NO_ERROR;

	log << tcu::TestLog::Message << "Checking call to " << functionName << tcu::TestLog::EndMessage;

	error = gl.getError();

	if (error != GL_INVALID_ENUM)
	{
		log << tcu::TestLog::Message << " returned wrong value [" << glu::getErrorStr(error) << ", expected " << glu::getErrorStr(GL_INVALID_ENUM) << "]" << tcu::TestLog::EndMessage;
		isOk = false;
	}

	return isOk;
}

FboSRGBUnsupportedEnumCase::IterateResult FboSRGBUnsupportedEnumCase::iterate (void)
{
	// TEST INFO:
	// API tests that check calls using enum GL_FRAMEBUFFER_SRGB return GL_INVALID_ENUM  when GL_EXT_sRGB_write_control is not supported

	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	bool					allPass		= true;
	glw::GLboolean			bEnabled	= GL_FALSE;
	glw::GLfloat			fEnabled	= 0;
	glw::GLint				iEnabled	= 0;
	glw::GLint64			lEnabled	= 0;

	m_context.getTestContext().getLog() << tcu::TestLog::Message
										<< "Check calls using enum GL_FRAMEBUFFER_SRGB return GL_INVALID_ENUM  when GL_EXT_sRGB_write_control is not supported\n\n"
										<< tcu::TestLog::EndMessage;

	gl.enable(GL_FRAMEBUFFER_SRGB);
	allPass &= isInvalidEnum("glEnable()");

	gl.disable(GL_FRAMEBUFFER_SRGB);
	allPass &= isInvalidEnum("glDisable()");

	gl.isEnabled(GL_FRAMEBUFFER_SRGB);
	allPass &= isInvalidEnum("glIsEnabled()");

	gl.getBooleanv(GL_FRAMEBUFFER_SRGB, &bEnabled);
	allPass &= isInvalidEnum("glGetBooleanv()");

	gl.getFloatv(GL_FRAMEBUFFER_SRGB, &fEnabled);
	allPass &= isInvalidEnum("glGetFloatv()");

	gl.getIntegerv(GL_FRAMEBUFFER_SRGB, &iEnabled);
	allPass &= isInvalidEnum("glGetIntegerv()");

	gl.getInteger64v(GL_FRAMEBUFFER_SRGB, &lEnabled);
	allPass &= isInvalidEnum("glGetInteger64v()");

	if (allPass)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

	return STOP;
}

} // anonymous

FboSRGBWriteControlTests::FboSRGBWriteControlTests	(Context& context)
	: TestCaseGroup			(context, "srgb_write_control", "Colorbuffer tests")
{
}

FboSRGBWriteControlTests::~FboSRGBWriteControlTests (void)
{
}

void FboSRGBWriteControlTests::init (void)
{
	this->addChild(new FboSRGBQueryCase					(m_context, "framebuffer_srgb_enabled",							"srgb enable framebuffer"));
	this->addChild(new FboSRGBColAttachCase				(m_context, "framebuffer_srgb_enabled_col_attach",				"srgb enable color attachment and framebuffer"));
	this->addChild(new FboSRGBToggleBlendCase			(m_context, "framebuffer_srgb_enabled_blend",					"toggle framebuffer srgb settings with blend disabled"));
	this->addChild(new FboSRGBRenderTargetIgnoreCase	(m_context, "framebuffer_srgb_enabled_render_target_ignore",	"enable framebuffer srgb, non-srgb render target should ignore"));
	this->addChild(new FboSRGBCopyToLinearCase			(m_context, "framebuffer_srgb_enabled_copy_to_linear",			"no conversion when blittering between framebuffer srgb and linear"));

	// negative
	this->addChild(new FboSRGBUnsupportedEnumCase		(m_context, "framebuffer_srgb_unsupported_enum",				"check error codes for query functions when extension is not supported"));
}

}
} // gles31
} // deqp
